﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Odyssey.Classes;
using System.Windows.Data;
using System.Linq;
using System.Windows.Controls.Primitives;
using Odyssey.ComponentModel;
using System.ComponentModel;
using Microsoft.Phone.Controls;
using System.Diagnostics;

namespace Odyssey.Controls
{
    /// <summary>
    /// ListBox that supports ICollectionView as ItemsSource to enabled grouping and jump lists. See also class <seealso cref="JumpListCollectionView"./>
    /// Features:
    /// + grouping of items with ICollectionView
    /// + quick jump to a group with jump list that pops up when a group item is tapped.
    /// + support for ICollectionView and IJumpListCollectionView.
    /// + support for dynamic ItemTemplates with ItemTemplates resource dictionary.
    /// + DataTemplateSelector support.
    /// </summary>
    [TemplatePart(Name = partPopup)]
    public class OdcListBox : ListBox
    {
        private VisualStateGroup scrollViewerVisualStateGroup;
        private ScrollViewer scrollViewer;
        private FrameworkElement viewport;
        private bool hasLazyItems;
        //  private List<ILazyDataItem> visibleItems = new List<ILazyDataItem>();

        private class PopupInfo
        {
            public Popup Popup;
            public Panel OverlayPanel;
            public PhoneApplicationPage Page;
            public JumpListPicker Picker;

        }

        const string partPopup = "PART_POPUP";
        private PopupInfo popupInfo;
        private ICollectionView collectionView;
        private ItemsControlHelper helper;

        public OdcListBox()
            : base()
        {
            Loaded += new RoutedEventHandler(OnLoaded);
            base.DefaultStyleKey = typeof(OdcListBox);
            helper = new ItemsControlHelper(this);

        }


        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            AttachScrollingEvents();
            UpdateLazyItems();
        }



        private double VerticalOffset
        {
            get { return (double)GetValue(VerticalOffsetProperty); }
            set { SetValue(VerticalOffsetProperty, value); }
        }

        public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register("VerticalOffset", typeof(double), typeof(OdcListBox), new PropertyMetadata(0.0, OnVerticalOffsetChanged));



        private static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            OdcListBox listBox = o as OdcListBox;
            listBox.OnVerticalOffsetChanged((double)e.OldValue, (double)e.NewValue);
        }

        private void OnVerticalOffsetChanged(double oldValue, double newValue)
        {
            if (IsScrolling) return;
            UpdateLazyItems();

        }


        /// <summary>
        /// Indicates whether the content of the listbox is currently scrolling.
        /// </summary>
        public bool IsScrolling
        {
            get { return (bool)GetValue(IsScrollingProperty); }
            private set { SetValue(IsScrollingProperty, value); }
        }

        public static readonly DependencyProperty IsScrollingProperty =
            DependencyProperty.Register("IsScrolling", typeof(bool), typeof(OdcListBox), new PropertyMetadata(false, OnScrollingPropertyChanged));

        private static void OnScrollingPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            OdcListBox box = o as OdcListBox;
            bool value = (bool)e.NewValue;
            box.OnScrollingChanged(value);
        }

        /// <summary>
        /// Occurs when IsScrolling property changes.
        /// </summary>
        /// <param name="value">current value of IsScrolling.</param>
        protected void OnScrollingChanged(bool value)
        {
            if (!value) UpdateLazyItems(); else StopAsyncItemLoading();
            var handler = ScrollingChanged;
            if (handler != null) handler(this, new ScrollingEventArgs(value));
        }


        /// <summary>
        /// Gets or sets the custom logic for choosing a template used to display each item.
        /// </summary>
        public DataTemplateSelector DataTemplateSelector
        {
            get { return (DataTemplateSelector)GetValue(DataTemplateSelectorProperty); }
            set { SetValue(DataTemplateSelectorProperty, value); }
        }

        /// <summary>
        /// Gets or sets the custom logic for choosing a template used to display each item.
        /// </summary>
        public static readonly DependencyProperty DataTemplateSelectorProperty =
            DependencyProperty.Register("DataTemplateSelector", typeof(DataTemplateSelector), typeof(OdcListBox), new PropertyMetadata(null));


        /// <summary>
        /// ResourceDictionary with either DataTemplate or DataTemplateRef classes for individual templates where the key usually is equal to the name of the
        /// class of the data item the template reflects.
        /// </summary>
        public ResourceDictionary ItemTemplates
        {
            get { return (ResourceDictionary)GetValue(ItemTemplatesProperty); }
            set { SetValue(ItemTemplatesProperty, value); }
        }

        /// <summary>
        /// ResourceDictionary with either DataTemplate or DataTemplateRef classes for individual templates where the key usually is equal to the name of the
        /// class of the data item the template reflects.
        /// </summary>
        public static readonly DependencyProperty ItemTemplatesProperty =
            DependencyProperty.Register("ItemTemplates", typeof(ResourceDictionary), typeof(OdcListBox), new PropertyMetadata(null));


        /// <summary>
        /// Gets or sets the style for the popup picker container.
        /// </summary>
        public Style JumpListStyle
        {
            get { return (Style)GetValue(JumpListStyleProperty); }
            set { SetValue(JumpListStyleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the style for the popup picker container.
        /// </summary>
        public static readonly DependencyProperty JumpListStyleProperty =
            DependencyProperty.Register("JumpListStyle", typeof(Style), typeof(OdcListBox), new PropertyMetadata(null));


        /// <summary>
        /// Gets or sets the template for the JumpListGroup in the picker popup.
        /// </summary>
        public DataTemplate JumpListItemTemplate
        {
            get { return (DataTemplate)GetValue(JumpListItemTemplateProperty); }
            set { SetValue(JumpListItemTemplateProperty, value); }
        }

        /// <summary>
        /// Gets or sets the template for the JumpListGroup in the picker popup.
        /// </summary>
        public static readonly DependencyProperty JumpListItemTemplateProperty =
            DependencyProperty.Register("JumpListItemTemplate", typeof(DataTemplate), typeof(OdcListBox), new PropertyMetadata(null));

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            collectionView = ItemsSource as ICollectionView;
        }



        /// <summary>
        /// Gets or sets the style for a listbox group item.
        /// </summary>
        public Style GroupStyle
        {
            get { return (Style)GetValue(GroupStyleProperty); }
            set { SetValue(GroupStyleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the style for a listbox group item.
        /// </summary>
        public static readonly DependencyProperty GroupStyleProperty =
            DependencyProperty.Register("GroupStyle", typeof(Style), typeof(OdcListBox), new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets the template for the header of a group.
        /// </summary>
        public DataTemplate GroupItemTemplate
        {
            get { return (DataTemplate)GetValue(GroupItemTemplateProperty); }
            set { SetValue(GroupItemTemplateProperty, value); }
        }

        /// <summary>
        /// Gets or sets the template for the header of a group.
        /// </summary>
        public static readonly DependencyProperty GroupItemTemplateProperty =
            DependencyProperty.Register("GroupItemTemplate", typeof(DataTemplate), typeof(OdcListBox), new PropertyMetadata(null, OnGroupItemTemplatePropertyChanged));

        private static void OnGroupItemTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            OdcListBox listBox = d as OdcListBox;
            listBox.OnGroupItemTemplateChanged(e.OldValue as DataTemplate, e.NewValue as DataTemplate);
        }

        protected virtual void OnGroupItemTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
        {

        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is OdcListBoxItem;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {

            OdcListBoxItem item = new OdcListBoxItem();
            Style style = ItemContainerStyle;
            if (style != null) item.Style = style;
            return item;
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);

            if (!SetGroupTemplate(element, item))
            {
                SetDynamicTemplate(element, item);
                DataTemplateSelector selector = DataTemplateSelector;
                if (selector != null) selector.SelectTemplate(item, element);
            }
            ILazyDataItem lazyItem = item as ILazyDataItem;
            if (lazyItem != null)
            {
                hasLazyItems = true;
            }
        }


        /// <summary>
        /// Sets the template for the item depending of the type of the item class.
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        private void SetDynamicTemplate(DependencyObject element, object item)
        {
            ResourceDictionary itemTemplates = ItemTemplates;
            if (itemTemplates != null && itemTemplates.Count > 0)
            {
                string typeName = item.GetType().Name;
                object rawTemplate = itemTemplates[typeName];
                if (null != rawTemplate)
                {
                    OdcListBoxItem listBoxItem = element as OdcListBoxItem;
                    DataTemplate dataTemplate = rawTemplate as DataTemplate;
                    if (dataTemplate != null)
                    {
                        listBoxItem.ContentTemplate = dataTemplate;
                    }
                    else
                    {
                        DataTemplateRef templateRef = rawTemplate as DataTemplateRef;
                        if (null != templateRef)
                        {
                            listBoxItem.ContentTemplate = templateRef.DataTemplate;
                        }
                    }
                }
            }
        }

        private bool SetGroupTemplate(DependencyObject element, object item)
        {
            OdcListBoxItem jumpListItem = element as OdcListBoxItem;
            bool result = jumpListItem != null;
            if (result)
            {
                bool isHeader = item is JumpListGroup;
                jumpListItem.IsHeader = isHeader;
                if (isHeader)
                {
                    jumpListItem.ContentTemplate = GroupItemTemplate;
                    Style style = GroupStyle;
                    if (style != null) jumpListItem.Style = style;
                }
            }
            return result;
        }


        /// <summary>
        /// Occurs when a item is tapped which contains a header.
        /// </summary>
        /// <param name="OdcListBoxItem"></param>
        internal protected void OnHeaderItemTap(OdcListBoxItem item)
        {
            OpenPopup();
        }

        protected void OpenPopup()
        {
            popupInfo = new PopupInfo();
            Popup popup = popupInfo.Popup = new Popup();
            Panel overlayPanel = popupInfo.OverlayPanel = new Grid();

            JumpListPicker listBox = popupInfo.Picker = new JumpListPicker();
            listBox.Closed += (s, e) => popup.IsOpen = false;
            listBox.Style = JumpListStyle;
            listBox.ItemTemplate = this.JumpListItemTemplate;
            listBox.ItemsSource = GetPickerItems();
            PhoneApplicationFrame rootVisual = Application.Current.RootVisual as PhoneApplicationFrame;
            PhoneApplicationPage page = popupInfo.Page = rootVisual.Content as PhoneApplicationPage;

            page.SizeChanged += OnRootVisualSizeChanged;
            page.BackKeyPress += new EventHandler<CancelEventArgs>(OnBackKeyPress);

            listBox.Unloaded += new RoutedEventHandler(listBox_Unloaded);
            listBox.SelectionChanged += new SelectionChangedEventHandler(OnGroupSelected);
            UpdateContextMenuPlacement();
            overlayPanel.Children.Add(listBox);
            popup.Child = overlayPanel;
            popup.IsOpen = true;
            listBox.Show();
        }

        void OnBackKeyPress(object sender, CancelEventArgs e)
        {
            if (popupInfo != null)
            {
                e.Cancel = true;
                ClosePopup();
            }
        }

        void listBox_Unloaded(object sender, RoutedEventArgs e)
        {
            if (sender == null) throw new ArgumentNullException();
        }

        void OnRootVisualSizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateContextMenuPlacement();
        }

        /// <summary>
        /// Updates the location and size of the Popup and overlay.
        /// </summary>
        private void UpdateContextMenuPlacement()
        {
            if (popupInfo == null) return;
            Popup popup = popupInfo.Popup;
            Panel overlayPanel = popupInfo.OverlayPanel;

            PhoneApplicationPage page = popupInfo.Page;
            PageOrientation orientation = page.Orientation;

            PhoneApplicationFrame rootVisual = Application.Current.RootVisual as PhoneApplicationFrame;
            TransformGroup transforms = new TransformGroup();
            if (null != page)
            {
                switch (orientation)
                {
                    case PageOrientation.LandscapeLeft:
                        transforms.Children.Add(new RotateTransform { Angle = 90 });
                        transforms.Children.Add(new TranslateTransform { X = rootVisual.ActualWidth });
                        break;

                    case PageOrientation.LandscapeRight:
                        transforms.Children.Add(new RotateTransform { Angle = -90 });
                        transforms.Children.Add(new TranslateTransform { Y = rootVisual.ActualHeight });
                        break;

                }
            }
            overlayPanel.RenderTransform = transforms;

            bool isLandscape = (orientation == PageOrientation.LandscapeLeft || orientation == PageOrientation.LandscapeRight);

            Rect bounds = new Rect(0, 0, page.ActualWidth, page.ActualHeight);
            bounds = page.TransformToVisual(rootVisual).TransformBounds(bounds);

            popup.VerticalOffset = Math.Max(0, isLandscape ? bounds.X : bounds.Y);
            popup.HorizontalOffset = Math.Max(0, isLandscape ? bounds.Y : bounds.X);
            overlayPanel.Width = bounds.Width;
            overlayPanel.Height = bounds.Height;
        }

        private IEnumerable GetPickerItems()
        {
            if (this.collectionView == null) return null;

            return collectionView.Groups;
        }

        void OnGroupSelected(object sender, SelectionChangedEventArgs e)
        {
            Selector selector = sender as Selector;
            JumpListGroup group = selector.SelectedItem as JumpListGroup;
            if (!group.IsEmpty)
            {
                int index = Items.IndexOf(group);
                if (index >= 0)
                {
                    helper.ScrollHost.ScrollToVerticalOffset(index);
                }
                ClosePopup();
            }
        }

        private void ClosePopup()
        {
            popupInfo.Picker.Close();
            //popupInfo.Popup.IsOpen = false;
            popupInfo.Page.SizeChanged -= OnRootVisualSizeChanged;
            popupInfo.Page.BackKeyPress -= OnBackKeyPress;
            popupInfo = null;
        }

        public override void OnApplyTemplate()
        {
            if (scrollViewerVisualStateGroup != null) DetachScrollingEvents(scrollViewerVisualStateGroup);
            scrollViewerVisualStateGroup = null;
            base.OnApplyTemplate();

            scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
            Debug.Assert(scrollViewer != null, "OdcListBox has no ScrollViewer control in template.");

            if (scrollViewer != null)
            {
                Binding b = new Binding("VerticalOffset");
                b.Mode = BindingMode.OneWay;
                b.Source = scrollViewer;
                this.SetBinding(OdcListBox.VerticalOffsetProperty, b);
            }

            viewport = GetViewPort();
        }


        private void DetachScrollingEvents(VisualStateGroup group)
        {
            if (DesignerProperties.IsInDesignTool) return;
            scrollViewerVisualStateGroup.CurrentStateChanging -= OnScrollingStateChanging;
        }

        void OnScrollingStateChanging(object sender, VisualStateChangedEventArgs e)
        {
            IsScrolling = (e.NewState.Name == "Scrolling");
        }

        private void AttachScrollingEvents()
        {
            if (DesignerProperties.IsInDesignTool || scrollViewer == null) return;

            if (VisualTreeHelper.GetChildrenCount(scrollViewer) > 0)
            {
                FrameworkElement child = scrollViewer.GetVisualChild(0);
                scrollViewerVisualStateGroup = child.GetVisualStateGroup("ScrollStates", true);
                if (scrollViewerVisualStateGroup != null)
                {
                    scrollViewerVisualStateGroup.CurrentStateChanging += this.OnScrollingStateChanging;
                }
            }
            else
            {
                Debug.WriteLine("ScrollViewer has no visual children.");
            }

        }

        /// <summary>
        /// Removes any bindings and templates applied to the item container for the specified content.
        /// </summary>
        /// <param name="element">The combo box item used to display the specified content.</param>
        /// <param name="item">The item content.</param>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            base.ClearContainerForItemOverride(element, item);
            UnloadLazyItem(item);
        }

        private void UnloadLazyItem(object item)
        {
            if (hasLazyItems)
            {
                ILazyDataItem lazyItem = item as ILazyDataItem;
                if (lazyItem != null)
                {
                    UnloadLazyItem(lazyItem);
                }
            }
        }


        private void StopAsyncItemLoading()
        {
            if (hasLazyItems)
            {
                ItemContainerGenerator gen = ItemContainerGenerator;
                foreach (var container in GetVisibleItems())
                {
                    ILazyDataItem lazyItem = gen.ItemFromContainer(container) as ILazyDataItem;
                    if (lazyItem != null)
                    {
                        PreloadLazyItem(lazyItem);
                    }
                }
            }
        }


        private void UpdateLazyItems()
        {
            Dispatcher.BeginInvoke(() => UpdateLazyItemsDispatched());
        }

        private void UpdateLazyItemsDispatched()
        {
            if (IsScrolling) return;
            if (hasLazyItems)
            {
                ItemContainerGenerator gen = ItemContainerGenerator;
                foreach (var container in this.GetVisualDescendants().OfType<OdcListBoxItem>())
                {
                    ILazyDataItem lazyItem = gen.ItemFromContainer(container) as ILazyDataItem;
                    if (lazyItem != null)
                    {
                        bool isVisible = IsItemVisible(container);
                        if (isVisible)
                        {
                            LoadLazyItem(lazyItem);
                        }
                        else
                        {
                            UnloadLazyItem(lazyItem);
                        }
                    }
                }
            }
        }

        private void UnloadLazyItem(ILazyDataItem lazyItem)
        {
            lazyItem.Unload();
        }


        private void LoadLazyItem(ILazyDataItem lazyItem)
        {
            lazyItem.Load();
        }


        private void PreloadLazyItem(ILazyDataItem lazyItem)
        {
            lazyItem.Scroll();
        }


        /// <summary>
        /// Gets all items that are in the visible area of the listbox.
        /// </summary>
        protected virtual IEnumerable<OdcListBoxItem> GetVisibleItems()
        {
            var items = this.GetVisualDescendants()
                .OfType<OdcListBoxItem>()
                .SkipWhile(item => !IsItemVisible(item))
                .TakeWhile(item => IsItemVisible(item));

            return items;
        }

        private FrameworkElement GetViewPort()
        {
            ScrollContentPresenter scp = scrollViewer == null ? null : scrollViewer.GetVisualDescendants().OfType<ScrollContentPresenter>().FirstOrDefault();
            return scp != null ? (FrameworkElement)scp : (FrameworkElement)scrollViewer;
        }

        private bool IsItemVisible(OdcListBoxItem item)
        {
            double w = viewport.ActualWidth;
            double h = viewport.ActualHeight;
            if (w == 0.0 && h == 0.0) return false;
            Rect itemsHostRect = new Rect(0.0, 0.0, w, h);

            FrameworkElement viewPort = GetViewPort();
            GeneralTransform transform = item.TransformToVisual(viewport);
            Rect listBoxItemRect = new Rect(transform.Transform(new Point()), transform.Transform(new Point(item.ActualWidth, item.ActualHeight)));

            const double threshold = 80;
            return ((listBoxItemRect.Bottom + threshold >= itemsHostRect.Top) && (listBoxItemRect.Top - threshold <= itemsHostRect.Bottom));
        }

        #region events

        /// <summary>
        /// Occurs when IsScrolling changes.
        /// </summary>
        public event EventHandler<ScrollingEventArgs> ScrollingChanged;

        #endregion

    }

}
